package com.hzdaba.aiapi.service.impl;

import com.hzdaba.aiapi.domain.AiNumList;
import com.hzdaba.aiapi.dto.NumsImportDto;
import com.hzdaba.aiapi.mapper.AiNumListMapper;
import com.hzdaba.aiapi.runnable.NumsImportRunnable;
import com.hzdaba.aiapi.service.AiNumListService;
import com.hzdaba.aiapi.vo.AiApiResponse;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

@Service
public class AiNumListServiceImpl implements AiNumListService {

    /**
     * 访问号码数据
     */
    @Autowired
    private AiNumListMapper aiNumListMapper;

    /**
     * 访问 Redis
     */
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * Spring 应用上下文
     */
    @Autowired
    ApplicationContext applicationContext;

    /**
     * 处理任务的线程池
     */
//    @Qualifier("numberImportExecutorService")
    @Qualifier("${aiapi.ainumlist.import.thread.pool.name}")
    @Autowired
    private ExecutorService executorService;

    /**
     * 每次向数据库导入的号码数量
     */
    @Value("${aiapi.ainumlist.import.batch.size}")
    private int batchOfImport;

    /**
     * 记录每笔请求需要导入号码数的 key
     */
    @Value("${aiapi.ainumlist.import.batches.expected.count.cache.key}")
    private String keyOfExpectedCount;

    /**
     * 缓存每笔导入请求回调地址的 key
     */
    @Value("${aiapi.ainumlist.import.batches.respurl.cache.key}")
    private String keyOfRespUrl;

    /**
     * 每批号码成功导入数的过期时间，单位分钟
     */
    @Value("${aiapi.ainumlist.import.batches.success.count.cache.expiry}")
    private int expiryOfSuccess;

    /**
     * 缓存导入失败的批次 id
     */
    @Value("${aiapi.ainumlist.import.batches.failure.batchid.cache.name}")
    private String keyOfFailedBatch;

    @Override
    public AiApiResponse importNums(NumsImportDto numsImportDto) {
        try {
            //检查是否有重复号码
            //其实用 Set 类型的参数接受预导入的号码也能达到去重的效果，但就是 Set 比 List 更占用内存，也没办法知道调用方实际要导入的数量

            //所有号码
            Set<String> phoneSet = new HashSet<>(numsImportDto.getList().size());

            for (AiNumList aiNum : numsImportDto.getList()) {//便利所有号码
                if (phoneSet.contains(aiNum.getPhone())) {//重复号码
                    return AiApiResponse.fail("抱歉，导入失败，重复号码：" + aiNum.getPhone());
                } else {//先前没有出现过的号码
                    phoneSet.add(aiNum.getPhone());
                }
            }

            //检查所有号码是否是同一批次
            String batchId = numsImportDto.getList().get(0).getBatchId();

            if (batchId == null || batchId.length() == 0) {//批次id为空
                return AiApiResponse.fail("批次 id 不可为空");
            }

            for (int i = 1; i < numsImportDto.getList().size(); i++) {
                if (!batchId.equals(numsImportDto.getList().get(i).getBatchId())) {//批次和第0个不一致
                    return AiApiResponse.fail("抱歉，一次请求导入的号码必须是同一批次的");
                }
            }

            //生成每次导入号码的唯一键
            StringBuilder uniqueKeyBuilder = new StringBuilder(numsImportDto.getList().get(0).getBatchId())
                    .append(":")
                    .append(numsImportDto.getReqId());
//                .append(":")
//                .append(numsImportDto.getList().size());

            //检查这个批次的数据是否之前导入失败过
            Boolean isFailed = this.redisTemplate.opsForHash().hasKey(this.keyOfFailedBatch, numsImportDto.getList().get(0).getBatchId());

            if (isFailed) {
                return AiApiResponse.fail("抱歉，本批次数据先前已导入失败，请过段时间再重新导入");
            }

            //在 Redis 中记录应该要导入的号码数量
            this.redisTemplate.opsForHash().put(this.keyOfExpectedCount, uniqueKeyBuilder.toString(), numsImportDto.getList().size());
            this.redisTemplate.expire(this.keyOfExpectedCount, this.expiryOfSuccess, TimeUnit.MINUTES);
            //过期时间就不重新定义配置了，配置文件太乱了，和记录成功导入数的 key 共用一个配置值吧

            //在 Redis 中记录回调地址
            if (numsImportDto.getRespUrl() != null && numsImportDto.getRespUrl().length() > 0) {//回调地址不为空
                this.redisTemplate.opsForHash().put(this.keyOfRespUrl, uniqueKeyBuilder.toString(), numsImportDto.getRespUrl());
                this.redisTemplate.expire(this.keyOfRespUrl, this.expiryOfSuccess, TimeUnit.MINUTES);
                //过期时间就不重新定义配置了，配置文件太乱了，和记录成功导入数的 key 共用一个配置值吧
            }

            NumsImportRunnable numsImportRunnable = null;

            if (numsImportDto.getList().size() <= this.batchOfImport) {//一个任务对象就能完成
                //           NumsImportRunnable numsImportRunnable = new NumsImportRunnable();
                numsImportRunnable = this.applicationContext.getBean(NumsImportRunnable.class);//具体执行导入任务的对象

                //            numsImportRunnable.setAiNumListMapper(this.aiNumListMapper);
                numsImportRunnable.setUniqueKey(uniqueKeyBuilder.toString());
                numsImportRunnable.setAiNumList(numsImportDto.getList());
                numsImportRunnable.setBegin(0);
                numsImportRunnable.setEnd(numsImportDto.getList().size() - 1);

                this.executorService.execute(numsImportRunnable);

                return AiApiResponse.success("正在导入");
            }

            //需要拆分任务，不然一条 Sql 太长

            for (int i = 0; i < numsImportDto.getList().size() / this.batchOfImport; i++) {//一次跳过10条
                int begin = i * this.batchOfImport;// 0 * 10 、 1 * 10
                int end = begin + (this.batchOfImport - 1);// 0 + 9 、1 + 9

                //           NumsImportRunnable numsImportRunnable = new NumsImportRunnable();
                numsImportRunnable = this.applicationContext.getBean(NumsImportRunnable.class);

                //            numsImportRunnable.setAiNumListMapper(aiNumListMapper);
                numsImportRunnable.setUniqueKey(uniqueKeyBuilder.toString());
                numsImportRunnable.setAiNumList(numsImportDto.getList());
                numsImportRunnable.setBegin(begin);
                numsImportRunnable.setEnd(end);

                this.executorService.execute(numsImportRunnable);
            }

            int remaining = numsImportDto.getList().size() % this.batchOfImport;//号码数量对批次取余

            if (remaining > 0) {//有不完整的批次
                //           NumsImportRunnable numsImportRunnable = new NumsImportRunnable();
                numsImportRunnable = this.applicationContext.getBean(NumsImportRunnable.class);

                //            numsImportRunnable.setAiNumListMapper(aiNumListMapper);
                numsImportRunnable.setUniqueKey(uniqueKeyBuilder.toString());
                numsImportRunnable.setAiNumList(numsImportDto.getList());
                numsImportRunnable.setBegin(numsImportDto.getList().size() - remaining);//起始位置为号码数量-榆树
                numsImportRunnable.setEnd(numsImportDto.getList().size() - 1);//结束位置为号码长度-1

                this.executorService.execute(numsImportRunnable);
            }

            return AiApiResponse.success("正在导入");
        } catch (Exception e) {
            return AiApiResponse.fail("抱歉，导入号码失败，请检查同一批次不同请求导入的号码中是否有重复数据");
        }
    }

    @Transactional(timeout = 1)
    @Override
    public int deleteAiNumListByBatchId(String batchId) {
        return this.aiNumListMapper.deleteAiNumListByBatchId(batchId);
    }

}
